package com.example.demo.filter;

import com.example.demo.common.Func;
import com.example.demo.common.constant.RedisKey;
import com.example.demo.common.constant.ResultCode;
import com.example.demo.exception.VisitException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

@WebFilter(urlPatterns = {"/*"})
@Slf4j
@RequiredArgsConstructor
public class FirewallFilter implements Filter {
    private final RedisTemplate<String, String> redis;
    private final HandlerExceptionResolver handlerExceptionResolver;

    /**
     * 接口访问高并发时，可能会出现连续加载多次，导致白名单内容重复的问题
     * 第1种方案：用定时任务，在指定时间重新加载白名单
     * 第2种方案：在项目初始化时，加载一次白名单，同时将重置白名单的加载标识设置为false
     **/
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
        // 判断是否重新加载配置文件里的IP列表（/file/config/whitelist.txt）
        boolean isLoad = Boolean.parseBoolean(redis.opsForValue().get(RedisKey.CONFIG_WHITELIST_LOAD));
        if (isLoad) {
            // 加载配置文件里的白名单（/file/config/whitelist.txt）
            // 1、清空旧的白名单
            redis.delete(RedisKey.CONFIG_WHITELIST_LIST);

            // 2、加载新的白名单，有效期1天
            String filePath = Func.getRootPath() + "/file/config/whitelist.txt";
            Scanner scanner = new Scanner(new BufferedReader(new FileReader(filePath)));
            scanner.useDelimiter("/n");
            while (scanner.hasNext()) {
                String line = URLDecoder.decode(scanner.nextLine(), "utf8");
                if (!line.contains("key:")) {
                    redis.opsForList().rightPush(RedisKey.CONFIG_WHITELIST_LIST, line);
                }
            }
            scanner.close();
            redis.expire(RedisKey.CONFIG_WHITELIST_LIST, 1, TimeUnit.DAYS);
            log.info("the whiteList is {}", redis.opsForList().range(RedisKey.CONFIG_WHITELIST_LIST, 0, -1));

            // 3、设置：重置白名单的加载标识为false，即：不必加载
            redis.opsForValue().set(RedisKey.CONFIG_WHITELIST_LOAD, "false", 1, TimeUnit.DAYS);
        }

        // 获取访问者的IP地址
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        String ip = Func.getIP(request);

        // 判断当前访问者是否在白名单里
        List<String> whiteList = redis.opsForList().range(RedisKey.CONFIG_WHITELIST_LIST, 0, -1);
        if (whiteList != null) {
            if (!whiteList.contains(ip)) {
                log.info("{} not exist the whitelist", ip);
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                handlerExceptionResolver.resolveException(request, response, null, new VisitException(ResultCode.USER_NOTIN_WHITELIST));
                return;
            }
        }

        // 判断当前访问者是否在IP禁止名单里
        List<String> blackList = redis.opsForList().range(RedisKey.CONFIG_BLACKLIST_LIST, 0, -1);
        if (blackList != null) {
            if (blackList.contains(ip)) {
                log.info(ip + ResultCode.USER_IP_PROHIBIT.getMessage());
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                handlerExceptionResolver.resolveException(request, response, null, new VisitException(ResultCode.USER_IP_PROHIBIT));
                return;
            }
        }

        chain.doFilter(servletRequest, servletResponse);
    }
}
